package bibliotheque.nameserver;

import bibliotheque.events.ErrorOccuredEvent;
import bibliotheque.events.ErrorOccuredListener;
import bibliotheque.events.StateChangedEvent;
import bibliotheque.events.StateChangedListener;
import bibliotheque.nameserver.parser.Graphe;
import bibliotheque.nameserver.remote.NameServerRemoteIC;
import bibliotheque.nameserver.remote.NameServerRemoteIS;
import bibliotheque.nameserver.parser.ParserNameServer;
import bibliotheque.server.remote.ServerRemoteIC;
import bibliotheque.server.remote.ServerRemoteIN;
import bibliotheque.wave.WaveAgentRemote;
import java.io.File;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;
import java.util.ArrayList;
import java.rmi.RemoteException;
import java.rmi.server.UnicastRemoteObject;
import java.util.Enumeration;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.Random;
import java.util.Timer;
import java.util.TimerTask;
import java.util.concurrent.Semaphore;
import javax.swing.JFrame;
import javax.swing.JSplitPane;

public class NameServer extends UnicastRemoteObject implements NameServerRemoteIS, NameServerRemoteIC {
    
    private class TimertaskHello extends TimerTask {
        
        public TimertaskHello() {
            super();
        }
        
        public void run() {
            try {
                mutex.acquireUninterruptibly();
                if(state == NameServer.State.STARTING || state == NameServer.State.START || state == NameServer.State.STOPPING) {
                    try {
                        timertask_hello = new TimertaskHello();
                        timer_hello.schedule(timertask_hello, TIMER_HELLO);
                        designated_server.Hello(TIMER_HELLOTIMEOUT);
                    } catch (Exception ex) {
                        throw new Exception("TimertaskHello failed cause the designated server returned an error.");
                    }
                }
            } catch (Exception ex) {
                ErrorOccured(ex.getMessage());
                BrutalStop();
            }finally {
                mutex.release();
            }
        }
    }
    
    private class TimertaskHelloTimeout extends TimerTask {
        
        public TimertaskHelloTimeout() {
            super();
        }
        
        public void run() {
            mutex.acquireUninterruptibly();
            if(state == NameServer.State.STARTING || state == NameServer.State.START || state == NameServer.State.STOPPING) {
                ErrorOccured("TimertaskHelloTimeout occurs.");
                BrutalStop();
            }
            mutex.release();
        }
    }
    
    private class TimertaskStoppingTimeout extends TimerTask {
        
        public TimertaskStoppingTimeout() {
            super();
        }
        
        public void run() {
            mutex.acquireUninterruptibly();
            if(state == NameServer.State.STOPPING || state == NameServer.State.START) {
                ErrorOccured("TimertaskStoppingTimeout occurs.");
                BrutalStop();
            }
            mutex.release();
        }
    }
    
    public static enum State {SUBSCRIBING, STARTING, START, STOPPING, STOP };
    
    private static long TIMER_HELLO = 2000;
    private static long TIMER_HELLOTIMEOUT = 6000;
    private static long TIMER_STOPPING = 60000;
    
    private ArrayList<StateChangedListener> state_changed_listener;
    private ArrayList<ErrorOccuredListener> error_occured_listener;
    
    private State state;
    private Semaphore mutex;
    private ServerRemoteIN designated_server;
    private Hashtable<String, ServerRemoteIN> stubs;
    private Hashtable<String, ArrayList<String>> neighbours;
    
    private Timer timer_hello;
    private Timer timer_hellotimeout;
    private Timer timer_stoppingtimeout;
    private TimertaskHello timertask_hello;
    private TimertaskHelloTimeout timertask_hellotimeout;
    private TimertaskStoppingTimeout timertask_stoppingtimeout;
    
    private String service;
    private Registry registry;
    
    public NameServer() throws RemoteException {
        this.state_changed_listener = new ArrayList<StateChangedListener>();
        this.error_occured_listener = new ArrayList<ErrorOccuredListener>();
        this.state = State.STOP;
        this.timer_hello = new Timer();
        this.timer_hellotimeout = new Timer();
        this.timer_stoppingtimeout = new Timer();
        this.service = null;
        this.mutex = new Semaphore(1);
    }
    
    public void AddStateChangedListener(StateChangedListener listener) {
        this.state_changed_listener.add(listener);
    }
    
    public void RemoveStateChangedListener(StateChangedListener listener) {
        this.state_changed_listener.remove(listener);
    }
    
    public void StateChanged(String state){
        StateChangedEvent event = new StateChangedEvent(this, state);
        for(int i=0;i<this.state_changed_listener.size();i++) {
            this.state_changed_listener.get(i).StateChanged(event);
        }
    }
    
    public void AddErrorOccuredListener(ErrorOccuredListener listener) {
        this.error_occured_listener.add(listener);
    }
    
    public void RemoveErrorOccuredListener(ErrorOccuredListener listener) {
        this.error_occured_listener.remove(listener);
    }
    
    public void ErrorOccured(String error){
        ErrorOccuredEvent event = new ErrorOccuredEvent(this, error);
        for(int i=0;i<this.error_occured_listener.size();i++) {
            this.error_occured_listener.get(i).ErrorOccured(event);
        }
    }
    
    protected void BrutalStop() {
        if(this.timertask_hello != null) this.timertask_hello.cancel();
        if(this.timertask_hellotimeout != null) this.timertask_hellotimeout.cancel();
        if(this.timertask_stoppingtimeout != null) this.timertask_stoppingtimeout.cancel();
        this.ErrorOccured("Brutal stop.");
        try {
            this.registry.unbind(this.service);
        } catch (Exception ex) {
            this.ErrorOccured("BrutalStop failed cause the service cannot be unbind from RMIRegistry.");
        }
        
        this.state = State.STOP;
        this.StateChanged(this.state.toString());
    }
    
    private void SendNeighbours() throws RemoteException {
        Thread thread = new Thread() {
            public void run() {
                String key;
                ServerRemoteIN stub;
                ArrayList<String> neighbour_ips;
                Enumeration<String> keys;
                try {
                    mutex.acquireUninterruptibly();
                    
                    if(state == NameServer.State.STARTING) throw new Exception("SendNeighbours failed cause name_server state is starting.");
                    if(state == NameServer.State.START) throw new Exception("SendNeighbours failed cause name_server state is started.");
                    if(state == NameServer.State.STOPPING) throw new Exception("SendNeighbours failed name_cause server state is stopping.");
                    if(state == NameServer.State.STOP) throw new Exception("SendNeighbours failed cause name_server state is stopped.");
                    
                    keys = neighbours.keys();
                    while(keys.hasMoreElements()) {
                        int i;
                        key = keys.nextElement();
                        stub = stubs.get(key);
                        neighbour_ips = neighbours.get(key);
                        for(i=0;i<neighbour_ips.size();i++) {
                            try {
                                stub.AddNeighbour((WaveAgentRemote)stubs.get(neighbour_ips.get(i)));
                            } catch (Exception ex) {
                                throw new Exception("SendNeighbours failed cause the server " + key + " returns an error.");
                            }
                        }
                    }
                    
                    try {
                        designated_server.Hello(TIMER_HELLOTIMEOUT);
                    } catch (Exception ex) {
                        throw new Exception("Hello failed cause the designated_server returns an error.");
                    }
                    
                    timertask_hello = new TimertaskHello();
                    timertask_hellotimeout = new TimertaskHelloTimeout();
                    timer_hello.schedule(timertask_hello, TIMER_HELLO);
                    timer_hellotimeout.schedule(timertask_hellotimeout, TIMER_HELLOTIMEOUT);
                    state = NameServer.State.STARTING;
                    StateChanged(state.toString());
                } catch (Exception ex) {
                    ErrorOccured(ex.getMessage());
                    if(state != NameServer.State.STOP) BrutalStop();
                } finally {
                    mutex.release();
                }
            }
        };
        thread.start();
    }
    
    public void StartService(File file, String service, String ip, int port) {
        try {
            this.mutex.acquireUninterruptibly();
            if(this.state == State.SUBSCRIBING) throw new Exception("StartService failed cause name_server state is subscribing.");
            if(this.state == State.STARTING) throw new Exception("StartService failed cause name_server state is starting.");
            if(this.state == State.START) throw new Exception("StartService failed cause name_server state is started.");
            if(this.state == State.STOPPING) throw new Exception("StartService failed cause name_server state is stopping.");
            
            this.service = service;
            this.designated_server = null;
            this.stubs = new Hashtable<String, ServerRemoteIN>();
            
            try {
                this.neighbours = ParserNameServer.GetNeighbours(file,service);
            } catch (Exception ex) {
                throw new Exception("StartService failed cause Parse failed.");
            }
            
            try {
                this.registry = LocateRegistry.getRegistry(ip, port);
            } catch (Exception ex) {
                throw new Exception("StartService failed cause the RMIRegistry cannot be find.");
            }
            
            try {
                this.registry.bind(this.service, this);
            } catch (Exception ex) {
                throw new Exception("StartService failed cause the service cannot be registered to the RMIRegistry.");
            }
            
            this.state = State.SUBSCRIBING;
            this.StateChanged(this.state.toString());
        } catch (Exception ex) {
            this.ErrorOccured(ex.getMessage());
            if(this.state != State.STOP) this.BrutalStop();
        } finally {
            this.mutex.release();
        }
    }
    
    public void StopService() {
        String key;
        ServerRemoteIN stub;
        Enumeration<String> keys;
        try {
            this.mutex.acquireUninterruptibly();
            if(this.state == State.STOPPING) throw new Exception("StopService failed cause name_server state is stopping.");
            if(this.state == State.STOP) throw new Exception("StopService failed cause name_server state is stopped.");
            
            try {
                this.registry.unbind(this.service);
            } catch (Exception ex) {
                this.ErrorOccured("StopService failed cause the service cannot be unbind from RMIRegistry.");
            }
            
            if(this.state == State.SUBSCRIBING || this.state == State.STARTING) {
                keys = this.stubs.keys();
                while(keys.hasMoreElements()) {
                    key = keys.nextElement();
                    stub = this.stubs.get(key);
                    try {
                        stub.Stop();
                    } catch (Exception ex) {
                        throw new Exception("StopService failed cause a server return an error.");
                    }
                    this.stubs.remove(key);
                }
                this.state = State.STOP;
                this.StateChanged(this.state.toString());
            } else {
                this.timertask_stoppingtimeout = new TimertaskStoppingTimeout();
                this.timer_stoppingtimeout.schedule(this.timertask_stoppingtimeout, TIMER_STOPPING);
                
                try {
                    this.designated_server.Stop();
                } catch (Exception ex) {
                    throw new Exception("StopService failed cause the designated_server return an error.");
                }
                
                this.state = State.STOPPING;
                this.StateChanged(this.state.toString());
            }
        } catch (Exception ex) {
            this.ErrorOccured(ex.getMessage());
            if(this.state != State.STOP) this.BrutalStop();
        } finally {
            this.mutex.release();
        }
    }
    
    public void Subscribe(String ip, ServerRemoteIN serverIN) throws RemoteException {
        boolean fatal = false;
        try {
            this.mutex.acquireUninterruptibly();
            if(!this.neighbours.containsKey(ip)) throw new Exception("Subscribe failed cause server the server is unknown.");
            if(this.stubs.contains(ip)) throw new Exception("Subscribe failed cause server the server is already registered.");
            fatal = true;
            if(this.state == State.STARTING) throw new Exception("Subscribe failed cause name_server state is starting.");
            if(this.state == State.START) throw new Exception("Subscribe failed cause name_server state is started.");
            if(this.state == State.STOPPING) throw new Exception("Subscribe failed cause name_server state is stopping.");
            if(this.state == State.STOP) throw new Exception("Subscribe failed cause server name_server is stopped.");
            
            if(this.designated_server == null) this.designated_server = serverIN;
            this.stubs.put(ip, serverIN);
            if(this.stubs.size() == this.neighbours.size()) {
                this.SendNeighbours();
            }
        } catch (Exception ex) {
            if(fatal) {
                this.ErrorOccured(ex.getMessage());
                if(this.state != State.STOP) this.BrutalStop();
            }
            throw new RemoteException(ex.getMessage());
        } finally {
            mutex.release();
        }
    }
    
    public void Hello() throws RemoteException {
        Thread thread;
        try {
            this.mutex.acquireUninterruptibly();
            if(this.state == State.SUBSCRIBING) throw new Exception("Hello failed cause name_server state is subscribing.");
            if(this.state == State.STOP) throw new Exception("Hello failed cause name_server state is stopped.");
            
            this.timertask_hellotimeout.cancel();
            this.timertask_hellotimeout = new TimertaskHelloTimeout();
            this.timer_hellotimeout.schedule(this.timertask_hellotimeout, TIMER_HELLOTIMEOUT);
            
            if(this.state == State.STARTING) {
                thread = new Thread() {
                    public void run() {
                        try {
                            designated_server.Hello(TIMER_HELLOTIMEOUT);
                            timertask_hello.cancel();
                            timertask_hello = new TimertaskHello();
                            timer_hello.schedule(timertask_hello, TIMER_HELLO);
                            state = NameServer.State.START;
                            StateChanged(state.toString());
                        } catch (Exception ex) {
                            ErrorOccured("Hello failed cause the designated_server returns an error.\nBrutal stop.");
                            BrutalStop();
                        }
                    }
                };
                thread.start();
            }
        } catch (Exception ex) {
            this.ErrorOccured(ex.getMessage());
            if(this.state != State.STOP) this.BrutalStop();
            throw new RemoteException(ex.getMessage());
        } finally {
            mutex.release();
        }
    }
    
    public void Unsubscribe(String ip) throws RemoteException {
        boolean fatal = false;
        ServerRemoteIN stub;
        try {
            this.mutex.acquireUninterruptibly();
            if(!this.stubs.containsKey(ip)) throw new Exception("Unsubscribe failed cause server didn't subscribe.");
            fatal = true;
            if(this.state == State.STARTING) throw new Exception("Unsubscribe failed cause server name_server is starting.");
            if(this.state == State.START) throw new Exception("Unsubscribe failed cause server name_server is started.");
            if(this.state == State.STOPPING) throw new Exception("Unsubscribe failed cause server name_server is stopping.");
            if(this.state == State.STOP) throw new Exception("Unsubscribe failed cause server name_server is stopped.");
            
            stub = this.stubs.get(ip);
            if(this.designated_server == stub) this.designated_server = null;
            this.stubs.remove(ip);
        } catch (Exception ex) {
            if(fatal) {
                this.ErrorOccured(ex.getMessage());
                if(this.state != State.STOP) this.BrutalStop();
            }
            throw new RemoteException(ex.getMessage());
        } finally {
            mutex.release();
        }
    }
    
    public void Stopped() throws RemoteException {
        try {
            this.mutex.acquireUninterruptibly();
            if(this.state == State.SUBSCRIBING) throw new Exception("Stop failed cause name_server state is subscribing.");
            if(this.state == State.STARTING) throw new Exception("Stop failed cause name_server state is starting.");
            if(this.state == State.STOP) throw new Exception("Stop failed cause name_server state is stopped.");
            
            if(this.state == State.START) {
                this.timertask_hello.cancel();
                this.timertask_hellotimeout.cancel();
                try {
                    this.registry.unbind(this.service);
                } catch (Exception ex) {
                    this.ErrorOccured("Stop failed cause the RMIRegistry returned an error.");
                }
            } else {
                this.timertask_stoppingtimeout.cancel();
            }
            
            this.state = NameServer.State.STOP;
            this.StateChanged(state.toString());
        } catch (Exception ex) {
            this.ErrorOccured(ex.getMessage());
            if(this.state != State.STOP) this.BrutalStop();
            throw new RemoteException(ex.getMessage());
        } finally {
            mutex.release();
        }
    }
    
    public ServerRemoteIC GetClosestServer(String ip) throws RemoteException {
        try {
            this.mutex.acquireUninterruptibly();
            if(this.state == State.SUBSCRIBING) throw new Exception("GetClosestServer failed cause name_server state is subscribing.");
            if(this.state == State.STARTING) throw new Exception("GetClosestServer failed cause name_server state is starting.");
            if(this.state == State.STOPPING) throw new Exception("GetClosestServer failed cause name_server state is stopping.");
            if(this.state == State.STOP) throw new Exception("GetClosestServer failed cause name_server state is stopped.");
            
            int num = new Random().nextInt(this.stubs.size());
            
            String key = null;
            Enumeration<String> keys = this.stubs.keys();
            while(num >= 0) {
                key = keys.nextElement();
                num--;
            }
            
            return((ServerRemoteIC)this.stubs.get(key));
        } catch (Exception ex) {
            this.ErrorOccured(ex.getMessage());
            if(this.state != State.STOP) this.BrutalStop();
            throw new RemoteException(ex.getMessage());
        } finally {
            mutex.release();
        }
    }
    
    StringBuilder sBuilder = new StringBuilder();
    
    public StringBuilder BuildGraph() {
        
        this.sBuilder.append("digraph G {");
        
        String noeud;
        Enumeration enu = this.neighbours.keys();
        while (enu.hasMoreElements()){
            noeud = (String) enu.nextElement();
            Iterator iter = this.neighbours.get(noeud).iterator();
            while(iter.hasNext()){
                this.sBuilder.append("\""+noeud+"\" -> "+"\""+iter.next()+"\";");
            }
        }
        this.sBuilder.append("}");
        
        /*
        JFrame frame = new JFrame();
        JSplitPane splitpane = new JSplitPane();
        
        splitpane.setRightComponent(new Graphe(this.sBuilder));
        
        frame.add(splitpane);
        frame.setVisible(true);
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
         */
        
        return this.sBuilder;
    }
}
